<?php
/**
 * Created by PhpStorm.
 * User: Sky
 * Date: 2017/7/27
 * Time: 下午2:40
 */

namespace app\librarys;


use app\models\ApiExceptionLog;
use app\models\ApiOperateLog;
use app\models\Manager;
use app\models\User;
use yii\web\Controller;

class BaseApiController extends Controller
{
    public $enableCsrfValidation = false;
    public $layout = false;

    protected $_request = null;

    const ApiTimestampValidity = 60;  // api签名时间戳过期时间60秒

    const IsFormRequest = false;  // 是否采用form表单形式传递参数

    //region api基础方法

    /**
     * 接口返回通用数据结构
     * @param array|mixed $data
     * @param int $status [0:success]
     * @param string $message
     * @return string
     */
    public function renderJson($data = null, $status = 0, $message = 'success')
    {
        $result = [
            'status' => $status,
            'message' => $message,
            'data' => $data
        ];

        return json_encode($result, true);
    }

    /**
     * 获取post传入进来的某个参数值
     * @param $request
     * @param string $paramName 参数名
     * @param null|string|int|float $defaultValue
     * @return mixed|null
     */
    private function _getRequestParamValue($request, $paramName, $defaultValue = null)
    {
        $returnValue = null;
        $request = (array)$request;
        if (isset($request[$paramName])) {
            $returnValue = $request[$paramName];
        }
        if ($returnValue == null) {
            if (isset($defaultValue)) {
                $returnValue = $defaultValue;
            } else {
                echo $this->renderJson(null, ApiResponseCode::ParamMissingCode, ApiResponseCode::ParamMissingMessage . ': ' . $paramName);
                exit;
            }
        }
        return $returnValue;
    }

    /**
     * 接收form表单提交的参数
     * @param \Yii\web\Request $request
     * @param $paramName
     * @param null $defaultValue
     * @return null
     */
    private function _getRequestParamValueWithForm($request, $paramName, $defaultValue = null)
    {
        $returnValue = null;
        $returnValue = $request->post($paramName);
        if ($returnValue == null) {
            if (isset($defaultValue)) {
                $returnValue = $defaultValue;
            } else {
                echo $this->renderJson(null, ApiResponseCode::ParamMissingCode, ApiResponseCode::ParamMissingMessage . ': ' . $paramName);
                exit;
            }
        }
        return $returnValue;
    }

    /**
     * 获取当前这次post传入进来的某个参数值
     * @param string $paramName 参数名
     * @param null|string|int|float $defaultValue
     * @return mixed|null
     */
    protected function _getRequestValue($paramName, $defaultValue = null)
    {
        if (self::IsFormRequest == true) {
            return $this->_getRequestParamValueWithForm($this->_request, $paramName, $defaultValue);
        } else {
            return $this->_getRequestParamValue($this->_request, $paramName, $defaultValue);
        }
    }

    public function beforeAction($action)
    {
        try {
            if (self::IsFormRequest == true) {
                $this->_request = \Yii::$app->request;
            } else {
                $this->_request = json_decode(file_get_contents("php://input"));
            }

            // 记录API请求日志
            try {
                $is_api_record_log = \Yii::$app->params['is_api_record_log'];
                if ($is_api_record_log == true) {
                    //判断是通用，用户，商户
                    $apiType = 0;
                    $userId = null;
                    $userName = null;
                    if (\Yii::$app->session->has(User::SessionKey)) {
                        $apiType = 1;
                        $userId = User::getCurrentUser()['id'];
                        $userName = User::getCurrentUser()['name'];
                    } else if (\Yii::$app->session->has(Manager::SessionKey)) {
                        $apiType = 2;
                        $userId = Manager::getCurrentManager()['id'];
                        $userName = Manager::getCurrentManager()['name'];
                    }
                    $apiLogId = ApiOperateLog::add(
                        $action->controller->id,
                        $action->id,
                        $_POST,
                        $apiType,
                        $userId,
                        $userName,
                        $_SERVER['REMOTE_ADDR'],
                        $_SERVER['HTTP_USER_AGENT'],
                        $_SERVER['REQUEST_TIME']
                    );
                    \Yii::$app->session->set('request_api_operate_log_id', $apiLogId);
                    \Yii::$app->session->set('request_api_timestamp', Tools::getMillisecond());
                }
            } catch (\Exception $ex) {

            }


            if (YII_DEBUG != true) {
                // api sign auth
                if (!$this->_auth()) {
                    return false;
                }
            }
            return parent::beforeAction($action);
        } catch (\Exception $ex) {
            return false;
        }
    }

    /**
     * 重写基类runAction,已处理所有action异常
     * @param string $id
     * @param array $params
     * @return mixed|string
     */
    public function runAction($id, $params = [])
    {
        try {
            return parent::runAction($id, $params);
        } catch (\Exception $ex) {

            try {
                $is_api_record_log = \Yii::$app->params['is_api_record_log'];
                if ($is_api_record_log == true) {
                    // 记录异常日志
                    $logId = \Yii::$app->session->get('request_api_operate_log_id');
                    if ($logId != null) {
                        ApiOperateLog::setException($logId, $ex);

                        $operateLog = ApiOperateLog::getById($logId);
                        if ($operateLog != null) {
                            ApiExceptionLog::add(
                                $operateLog->controllerId,
                                $operateLog->actionId,
                                $operateLog->postParams,
                                $operateLog->apiType,
                                $operateLog->userId,
                                $operateLog->userName,
                                $operateLog->remoteAddr,
                                $operateLog->httpUserAgent,
                                $operateLog->createDatetime,
                                $ex
                            );
                        }
                    }
                }
            } catch (\Exception $ex) {
            }

            //返回错误信息
            return $this->_errorHandler($ex);
        }
    }

    public function afterAction($action, $result)
    {
        // TODO record api log
        try {
//            $is_api_record_log = \Yii::$app->params['is_api_record_log'];
//            if ($is_api_record_log == true) {
//                // 记录请求总共耗时(毫秒)
//                $apiLogId = \Yii::$app->session->get('request_api_operate_log_id');
//                if ($apiLogId != null) {
//                    $time = Tools::getMillisecond();
//                    $time = $time - \Yii::$app->session->get('request_api_timestamp');
//                    ApiOperateLog::setDurationTime($apiLogId, $time);
//                }
//            }
        } catch (\Exception $ex) {
        }
        return parent::afterAction($action, $result);
    }

    /**
     * 自定义的错误处理返回json
     * @param \Exception $ex
     * @return string
     */
    private function _errorHandler($ex)
    {
        try {
            throw $ex;
//        } catch (ApiAccountMissingException $e) {
//            if (YII_DEBUG == true) {
//                return $this->renderJson(null, ApiResponseCode::AccountMissingCode, ApiResponseCode::AccountMissingMessage);
//            } else {
//                return $this->renderJson(null, ApiResponseCode::ExceptionCode, ApiResponseCode::ExceptionMessage . ':' . $ex->getMessage());
//            }
        } catch (\Exception $e) {
            if (YII_DEBUG == true) {
                return $this->renderJson(null, ApiResponseCode::ExceptionCode, ApiResponseCode::ExceptionMessage . ':' . Tools::getExceptionFullTraceAsString($ex));
            } else {
                return $this->renderJson(null, ApiResponseCode::ExceptionCode, ApiResponseCode::ExceptionMessage . ':' . $ex->getMessage());
            }
        }
    }

    /**
     * api鉴权
     * @return bool
     */
    private function _auth()
    {
        // 缺少account参数
        $account = \Yii::$app->request->get('account');
        if ($account == null) {
            $this->_logException(new \Exception(ApiResponseCode::AccountMissingMessage, ApiResponseCode::AccountMissingCode));
            echo $this->renderJson(null, ApiResponseCode::AccountMissingCode, ApiResponseCode::AccountMissingMessage);  // 缺少账户信息 (1001)
            return false;
        }

        // 缺少timestamp参数
        $timestamp = \Yii::$app->request->get('timestamp');
        if ($timestamp == null) {
            $this->_logException(new \Exception(ApiResponseCode::TimestampMissingMessage, ApiResponseCode::TimestampMissingCode));
            echo $this->renderJson(null, ApiResponseCode::TimestampMissingCode, ApiResponseCode::TimestampMissingMessage);  // 缺少时间戳
            return false;
        }

        // 缺少verify参数
        $verify = \Yii::$app->request->get('verify');
        if ($verify == null) {
            $this->_logException(new \Exception(ApiResponseCode::VerifyMissingMessage, ApiResponseCode::VerifyMissingCode));
            echo $this->renderJson(null, ApiResponseCode::VerifyMissingCode, ApiResponseCode::VerifyMissingMessage);
            return false;
        }

        // 缺少sign参数
        $sign = \Yii::$app->request->get('sign');
        if ($sign == null) {
            $this->_logException(new \Exception(ApiResponseCode::SignMissingMessage, ApiResponseCode::SignMissingCode));
            echo $this->renderJson(null, ApiResponseCode::SignMissingCode, ApiResponseCode::SignMissingMessage);  // 缺少签名信息
            return false;
        }

        // timestamp过期
        if (abs(time() - $timestamp) > self::ApiTimestampValidity) {
            $this->_logException(new \Exception(ApiResponseCode::TimestampInvalidMessage, ApiResponseCode::TimestampInvalidCode));
            echo $this->renderJson(null, ApiResponseCode::TimestampInvalidCode, ApiResponseCode::TimestampInvalidMessage);  // 签名过期
            return false;
        }

        // sign认证
        $token = $this->_getAccountToken();
        $authSign = strtolower(md5($account . $token . $timestamp . $verify));
        if ($authSign != $sign) {
            $this->_logException(new \Exception(ApiResponseCode::SignAuthFailedMessage, ApiResponseCode::SignAuthFailedCode));
            echo $this->renderJson(null, ApiResponseCode::SignAuthFailedCode, ApiResponseCode::SignAuthFailedMessage);  // 签名认证失败
            return false;
        }

        // 请求校验
        if ($this->_checkVerify($account, $verify) == false) {
            $this->_logException(new \Exception(ApiResponseCode::VerifyInvalidMessage, ApiResponseCode::VerifyInvalidCode));
            echo $this->renderJson(null, ApiResponseCode::VerifyInvalidCode, ApiResponseCode::VerifyInvalidMessage);
            return false;
        }

        // 参数校验
        $request = (array)$this->_request;
        if (isset($request['sign']) == false && (count($request) > 0)) {  // 缺少参数签名
            $this->_logException(new \Exception(ApiResponseCode::ParamSignMissingMessage, ApiResponseCode::ParamSignMissingCode));
            echo $this->renderJson(null, ApiResponseCode::ParamSignMissingCode, ApiResponseCode::ParamSignMissingMessage);
            return false;
        }
        if (isset($request['sign']) == true) {
            $paramSign = $request['sign'];
            unset($request['sign']);
            Tools::arraySort($request);
            $paramAuthSign = strtolower(md5(json_encode($request) . $token));
            if ($paramAuthSign != $paramSign) {  // 参数签名认证失败失败
                $this->_logException(new \Exception(ApiResponseCode::ParamSignAuthFailedMessage, ApiResponseCode::ParamSignAuthFailedCode));
                echo $this->renderJson(null, ApiResponseCode::ParamSignAuthFailedCode, ApiResponseCode::ParamSignAuthFailedMessage);
                return false;
            }
        }

        return true;
    }

    /**
     * 获取account的token
     * @return string
     */
    private function _getAccountToken()
    {
        $token = '';  // TODO
        return $token;
    }

    /**
     * 校验verify。如果已经存在此verify,则此请求校验信息无效。如果不存在verify,则记录此verify。
     * @param $account
     * @param $verify
     * @return bool
     */
    private function _checkVerify($account, $verify)
    {
        $key = 'api_verify:' . $account . $verify;
        if (\Yii::$app->cache->exists($key)) {
            return false;
        } else {
            \Yii::$app->cache->set($key, null, 65);
            return true;
        }
    }

    /**
     * 记录当前会话的异常日志
     * @param $ex
     */
    private function _logException($ex)
    {
        // TODO record api exception log
    }

    //endregion
}